#include "TypeParser.h"
#include <fan-llvm/core/Core.h>

using namespace std;


//////////////////////////////////////////////////////////////////////////
// Factory
//////////////////////////////////////////////////////////////////////////


Type* TypeParser::load(const string& sig, bool checked, Pod* loadingPod)
{
  // if last character is ?, then parse a nullable
  int len = sig.length();
  char last = (len > 1) ? sig[len - 1] : 0;
  if (last == '?') {
    Type* type = TypeParser::load(sig.substr(0, len - 1), checked, loadingPod);
    return Type_toNullable(type);
  }

  // if the last character isn't ] or |, then this a non-generic
  // type and we don't even need to allocate a parser
  if (last != ']' && last != '|')
  {
    int colon = sig.find(':');
    if (colon > 0 && colon < (len - 2) && sig[colon + 1] == ':')
    {
      string podName = sig.substr(0, colon);
      string typeName = sig.substr(colon + 2);

      // if the type is from the pod being loaded then return to the pod
      if (loadingPod && podName == Str_cStr(Pod_name(loadingPod)))
        return Pod_type(loadingPod, Str_createFromCStr(typeName.c_str()), checked);
      
      // do a straight lookup
      return TypeParser::find(podName, typeName, checked);
    }
    else
    {
      exceptionThrow(TypeParser::err(sig));
      return NULL;
    }
  }
  
  // we got our work cut out for us - create parser
  return TypeParser(sig, checked, loadingPod).loadTop();
}


Type* TypeParser::find(const string& podName, const string& typeName, bool checked)
{
  Pod* pod = Pod_find(Str_createFromCStr(podName.c_str()), checked);
  if (pod == NULL) return NULL;
  return Pod_type(pod, Str_createFromCStr(typeName.c_str()), checked);
}


//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////


TypeParser::TypeParser(const string& sig, bool checked, Pod* loadingPod)
{
  if (sig.length() < 4) exceptionThrow(TypeParser::err(sig));
  m_sig = sig;
  m_len = sig.length();
  m_pos = 0;
  m_cur = sig[m_pos];
  m_peek = sig[m_pos + 1];
  m_checked = checked;
  m_loadingPod = loadingPod;
}


//////////////////////////////////////////////////////////////////////////
// Parse
//////////////////////////////////////////////////////////////////////////


Type* TypeParser::loadTop()
{
  Type* type = this->load();
  if (m_cur != 0) exceptionThrow(this->err());
  return type;
}


Type* TypeParser::load()
{
  Type* type;

  // |...| is func
  if (m_cur == '|')
    type = this->loadFunc();
  
  // [...] is map
  else if (m_cur == '[')
    type = this->loadMap();

  // otherwise must be basic[]
  else
    type = this->loadBasic();

  // nullable
  if (m_cur == '?')
  {
    this->consume('?');
    type = Type_toNullable(type);
  }

  // anything left must be []
  while (m_cur == '[')
  {
    this->consume('[');
    this->consume(']');
    type = Type_toListOf(type);
  }

  // nullable
  if (m_cur == '?')
  {
    this->consume('?');
    type = Type_toNullable(type);
  }

  return type;
}


Type* TypeParser::loadMap()
{
  this->consume('[');
  Type* key = this->load();
  this->consume(':');
  Type* val = this->load();
  this->consume(']');
  //TODO: return (Type*)MapType_create(key, val);
  return NULL;
}


Type* TypeParser::loadFunc()
{
  List* params = List_create(Type_classType(), 8);
  this->consume('|');
  if (m_cur != '-')
  {
    while (true)
    {
      List_add(params, VAL_MAKE_REF(this->load()));
      if (m_cur == '-') break;
      this->consume(',');
    }
  }
  this->consume('-');
  this->consume('>');
  Type* ret = this->load();
  this->consume('|');

  //TODO: return (Type*)FuncType_create(params, ret);
  return NULL;
}


Type* TypeParser::loadBasic()
{
  string podName = this->consumeId();
  this->consume(':');
  this->consume(':');
  string typeName = this->consumeId();

  // check for generic parameter like sys::V
  if (typeName.length() == 1 && podName == "sys")
  {
    Type* type = NULL;//TODO: Sys_genericParamType(typeName[0]);
    if (type != NULL) return type;
  }

  if (m_loadingPod != NULL && podName == Str_cStr(Pod_name(m_loadingPod)))
    return Pod_type(m_loadingPod, Str_createFromCStr(typeName.c_str()), m_checked);
  else
    return TypeParser::find(podName, typeName, m_checked);
}


//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////


string TypeParser::consumeId()
{
  int start = m_pos;
  while (Int_isAlphaNum(m_cur) || m_cur == '_') this->consume();
  return m_sig.substr(start, m_pos - start);
}


void TypeParser::consume(char expected)
{
  if (m_cur != expected) exceptionThrow(this->err());
  this->consume();
}


void TypeParser::consume()
{
  m_cur = m_peek;
  m_pos++;
  m_peek = (m_pos + 1 < m_len) ? m_sig[m_pos + 1] : 0;
}


Err* TypeParser::err()
{
  return TypeParser::err(m_sig);
}


Err* TypeParser::err(const std::string& sig)
{
  return (Err*)ArgErr_create(Str_createFromCStr(("Invalid type signature '" + sig + "'").c_str()), NULL);
}
